/* Copyright 2001,2002,2003 NAH6 BV
 * All Rights Reserved
 *
 *  $Header: /var/lib/cvs/secphone/ui/rng/fortuna/fnaAccumulator.cpp,v 1.4 2003/11/21 16:12:58 itsme Exp $
 *
 *
 * Design of the PRNG is literally taken from 'Practical Cryptography', by Niels Ferguson, and Bruce Schneier.
 *
 */


#include "fnaAccumulator.h"
#include "debug.h"
//--------------------------------------------------------------------------
Accumulator::Accumulator()
{
    Initialize();
}

Accumulator::~Accumulator()
{
    // on shutdown updateseedfile.
    // unfortunately most of the time we are just killed, and no shutdown
    // takes place. for that reason, we can either always call updateseedfile
    // after a call to RandomData, or always gather entropy from sources before
    // returning the first random.
    UpdateSeedFile();
}

void Accumulator::Initialize()
{
    // well, these 'initialize' calls are not really nescesary,
    // they are also called from each objects default constructor.
    for (int i=1 ; i<Accumulator::NROFPOOLS ; ++i)
        m_pools[i].Initialize();
    m_generator.Initialize();
    m_reseedcount= 0;

    debug("Accumulator::initialize()\n");
}

bool Accumulator::AddRandomEvent(sourcenumber_t source, poolnumber_t pool, const ByteVector& data)
{
    if (source<0 || source>=Accumulator::NROFSOURCES)
    {
        debug("Accumulator::AddRandomEvent: invalid source number\n");
        return false;
    }
    if (pool<0 || pool>=Accumulator::NROFPOOLS)
    {
        debug("Accumulator::AddRandomEvent: invalid pool number\n");
        return false;
    }
    if (data.size()<=0 || data.size()>32)
    {
        debug("Accumulator::AddRandomEvent: invalid data size\n");
        return false;
    }

    if (m_pools[pool].WaitForMutex())
    {
        m_pools[pool].Add((BYTE)source);
        m_pools[pool].Add((BYTE)data.size());
        m_pools[pool].Add(data);
        m_pools[pool].ReleaseMutex();

        return true;
    }

    debug("Accumulator::AddRandomEvent: could not get pool mutex\n");
    return false;
}

void Accumulator::DoReseed()
{
    debug("Accumulator::DoReseed\n");

    ByteVector seed=m_pools[0].Use();

    ++m_reseedcount;

    for (int i=1 ; i<Accumulator::NROFPOOLS ; ++i)
    {
        if (m_reseedcount&(1<<(i-1)))
            break;
        
        ByteVector poolvalue= m_pools[i].Use();
        seed.insert(seed.end(), poolvalue.begin(), poolvalue.end());
    }

    m_generator.Reseed(seed);
}

bool Accumulator::RandomData(int nBytesRequested, ByteVector& r)
{
    debug("Accumulator::RandomData pool[0]=%d,  time=%d\n", 
        m_pools[0].GetSize(), GetTickCount()-m_reseedtime);

    if (m_pools[0].GetSize() >= Accumulator::MINIMUMPOOLSIZE
            && GetTickCount()-m_reseedtime > Accumulator::MINIMUMRESEEDINTERVAL)
    {
        DoReseed();
        m_reseedtime= GetTickCount();
    }

    return m_generator.PseudoRandomData(nBytesRequested, r);
}

bool Accumulator::WriteSeedFile()
{
    ByteVector random;
    if (!RandomData(Accumulator::SEEDFILESIZE, random))
    {
        debug("Accumulator::WriteSeedFile: error getting random data\n");
        return false;
    }

    HANDLE hFile= CreateFile(L"seedfile.fna", GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL);
    if (hFile==NULL || hFile==INVALID_HANDLE_VALUE)
    {
        error("Accumulator::WriteSeedFile: error opening seedfile\n");
        return false;
    }

    bool bRes= true;

    DWORD nWritten;
    if (!WriteFile(hFile, vectorptr(random), random.size(), &nWritten, NULL))
    {
        error("Accumulator::WriteSeedFile: error opening seedfile\n");
        bRes= false;
    }

    if (nWritten!=random.size())
    {
        debug("Accumulator::WriteSeedFile: error writing seedfile\n");
        bRes= false;
    }

    if (!FlushFileBuffers(hFile))
    {
        error("Accumulator::WriteSeedFile: error flushing seedfile\n");
        bRes= false;
    }

    if (!CloseHandle(hFile))
    {
        error("Accumulator::WriteSeedFile: error closing seedfile\n");
        bRes= false;
    }

    return bRes;
}

// this function should be called regularly
bool Accumulator::UpdateSeedFile()
{
    if (!m_mutexUpdateSeed.Get())
    {
        debug("Accumulator::UpdateSeedFile: error getting mutex\n");
        return false;
    }

    // todo: do this safer:
    //     - delete seedfile.lck
    //     - rename seedfile.fna to seedfile.lck
    //     - read seedfile.lck
    //     - create seedfile.fna
    //     - delete seedfile.lck
    HANDLE hFile= CreateFile(L"seedfile.fna", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL);
    if (hFile==NULL || hFile==INVALID_HANDLE_VALUE)
    {
        error("Accumulator::UpdateSeedFile: error opening seedfile\n");
        m_mutexUpdateSeed.Release();
        return false;
    }

    ByteVector random(Accumulator::SEEDFILESIZE);

    bool bRes= true;

    DWORD nRead;
    if (!ReadFile(hFile, vectorptr(random), random.size(), &nRead, NULL))
    {
        error("Accumulator::UpdateSeedFile: error reading seedfile\n");
        bRes= false;
    }
    if (nRead!=random.size())
    {
        debug("Accumulator::UpdateSeedFile: error reading seedfile\n");
        bRes= false;
    }

    // make sure file is erased
    if (0!=SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
    {
        error("Accumulator::UpdateSeedFile: error truncating seedfile\n");
        bRes= false;
    }
    if (!SetEndOfFile(hFile))
    {
        error("Accumulator::UpdateSeedFile: error truncating seedfile\n");
        bRes= false;
    }

    AddSystemTimeToRandom(random);

    m_generator.Reseed(random);

    // reusing 'random' variable to  update seedfile

    if (!RandomData(Accumulator::SEEDFILESIZE, random))
    {
        debug("Accumulator::UpdateSeedFile: error getting random\n");
        bRes= false;    // should never happen.
    }

    DWORD nWritten;
    if (!WriteFile(hFile, vectorptr(random), random.size(), &nWritten, NULL))
    {
        error("Accumulator::UpdateSeedFile: error writing seedfile\n");
        bRes= false;
    }

    if (nWritten!=random.size())
    {
        debug("Accumulator::UpdateSeedFile: error writing seedfile\n");
        bRes= false;
    }

    if (!FlushFileBuffers(hFile))
    {
        error("Accumulator::UpdateSeedFile: error flushing seedfile\n");
        bRes= false;
    }

    if (!CloseHandle(hFile))
    {
        error("Accumulator::UpdateSeedFile: error closing seedfile\n");
        bRes= false;
    }

    m_properly_seeded= bRes;

    m_mutexUpdateSeed.Release();
    return bRes;
}
void Accumulator::AddSystemTimeToRandom(ByteVector& random)
{
    SYSTEMTIME systime[1];

    GetSystemTime(systime);

    ByteVector bytes((BYTE*)&systime[0], (BYTE*)&systime[1]);
    random.insert(random.end(), bytes.begin(), bytes.end());
}

